package com.cdk8s.example.simplespringboot.utils;

import cn.hutool.core.io.IORuntimeException;
import com.cdk8s.example.simplespringboot.utils.id.GenerateIdUtil;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.FilenameUtils;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.core.io.ClassPathResource;
import org.springframework.web.multipart.MultipartFile;

import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.SSLSession;
import javax.net.ssl.X509TrustManager;
import javax.servlet.http.HttpServletRequest;
import java.io.*;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;

/**
 * 文件操作工具类.
 */
@Slf4j
public final class FileUtil {

	public static String getNewFileNameByUUID() {
		return GenerateIdUtil.getUUID();
	}

	@SneakyThrows
	public static void deleteDirectory(String fullPath) {
		FileUtils.deleteDirectory(new File(fullPath));
	}

	/**
	 * 安静删除文件或目录以及子目录
	 * 不会抛异常出来
	 */
	public static void deleteQuietlyByFileOrDirectory(String fullPath) {
		FileUtils.deleteQuietly(new File(fullPath));
	}

	/**
	 * 删除文件或目录以及子目录
	 * 会抛异常出来
	 */
	@SneakyThrows
	public static void forceDelete(String fullPath) {
		FileUtils.forceDelete(new File(fullPath));
	}

	/**
	 * 获得指定目录下所有文件
	 * 不会扫描子目录
	 * 返回值类似如下："spring.txt","node.txt","mysql.txt"
	 *
	 * @param path 相对ClassPath的目录或者绝对路径目录
	 * @return 文件路径列表（如果是jar中的文件，则给定类似.jar!/xxx/xxx的路径）
	 * @throws IORuntimeException IO异常
	 */
	public static List<String> listFileNames(String path) throws IORuntimeException {
		return cn.hutool.core.io.FileUtil.listFileNames(path);
	}


	/**
	 * 递归遍历目录以及子目录中的所有文件
	 * 返回值类似如下效果：
	 * /Users/meek/Downloads/CentOS-7/big-data-software.zip
	 * /Users/meek/Downloads/CentOS-7/online-deploy-my-redis6-mysql8/jdk-8u261-linux-x64.tar.gz
	 * /Users/meek/Downloads/CentOS-7/online-deploy-my-redis6-mysql8/centos7.9-make-libs.zip
	 *
	 * @param path 当前遍历文件或目录的路径
	 * @return 文件列表
	 * @since 3.2.0
	 */
	public static List<File> loopFiles(String path) {
		return cn.hutool.core.io.FileUtil.loopFiles(path);
	}

	/**
	 * 修改文件或目录的文件名，不变更路径，只是简单修改文件名，不保留扩展名。<br>
	 *
	 * <pre>
	 * 源文件叫做：bbb.jpg
	 * FileUtil.rename(file, "aaa.mp4", true)
	 * 结果改名为：aaa.mp4（也就是传过来是啥后缀就直接填写上，即使改完文件无法打开也不管）
	 * </pre>
	 *
	 * @param file       被修改的文件
	 * @param newName    新的文件名，如需扩展名，需自行在此参数加上，原文件名的扩展名不会被保留
	 * @param isOverride 是否覆盖目标文件
	 */
	public static File rename(File file, String newName, boolean isOverride) {
		return cn.hutool.core.io.FileUtil.rename(file, newName, isOverride);
	}

	@SneakyThrows
	public static InputStream getStringToInputStream(String str) {
		return IOUtils.toInputStream(str, StandardCharsets.UTF_8.name());
	}

	@SneakyThrows
	public static InputStream getLocalFileToInputStream(String fullFilePath) {
		final File initialFile = new File(fullFilePath);
		return new DataInputStream(new FileInputStream(initialFile));
	}

	@SneakyThrows
	public static String getInputStreamToString(InputStream inputStream) {
		return IOUtils.toString(inputStream, StandardCharsets.UTF_8.name());
	}

	@SneakyThrows
	public static void writeStringToFile(File file, String content) {
		FileUtils.writeStringToFile(file, content, StandardCharsets.UTF_8);
	}

    @SneakyThrows
    public static void writeStringToFile(String fullFilePath, String content) {
        FileUtils.writeStringToFile(new File(fullFilePath), content, StandardCharsets.UTF_8);
    }

	public static void checkImageAndVideoFormat(MultipartFile file, Long maxSize) {
		String originalFilename = file.getOriginalFilename();
		if (StringUtil.isBlank(originalFilename)) {
			throw new RuntimeException("文件格式不正确");
		}

		// 校验文件格式
		boolean flag = false;
		if (FileUtil.checkFileTypeByImage(originalFilename)) {
			flag = true;
		}
		if (FileUtil.checkFileTypeByVideo(originalFilename)) {
			flag = true;
		}
		if (!flag) {
			throw new RuntimeException("只能选择图片、视频类型文件");
		}

		if (null == maxSize) {
			maxSize = 20971520L;
		}

		// 校验文件大小，不允许超过 10M，getSize 单位 bytes
		if (file.getSize() > maxSize) {
			String error = "文件过大！请上传小于 " + (maxSize / 1024 / 1024) + "M 的文件";
			throw new RuntimeException(error);
		}
	}

	public static void checkIconImageFormat(String originalFilename, Long fileBytesSize, Long maxBytesSize) {
		// 校验文件格式
		if (!FileUtil.checkFileTypeByImage(originalFilename)) {
			throw new RuntimeException("只能选择图片类型文件");
		}

		// 校验文件大小
		if (fileBytesSize > maxBytesSize) {
			String error = "图片过大！请上传小于 " + (maxBytesSize / 1024) + "KB 的图片";
			throw new RuntimeException(error);
		}
	}

	public static void checkImageFormat(MultipartFile file, Long maxSize) {
		String originalFilename = file.getOriginalFilename();
		if (StringUtil.isBlank(originalFilename)) {
			throw new RuntimeException("图片格式不正确");
		}

		// 校验文件格式
		if (!FileUtil.checkFileTypeByImage(originalFilename)) {
			throw new RuntimeException("只能选择图片类型文件");
		}

		if (null == maxSize) {
			maxSize = 10485760L;
		}

		// 校验文件大小，不允许超过 10M，getSize 单位 bytes
		if (file.getSize() > maxSize) {
			String error = "图片过大！请上传小于 " + (maxSize / 1024 / 1024) + "M 的图片";
			if (maxSize < (1024 * 1024)) {
				error = "图片过大！请上传小于 " + (maxSize / 1024) + "kb 的图片";
			}
			throw new RuntimeException(error);
		}
	}

	public static void checkVideoFormat(MultipartFile file, Long maxSize) {
		String fileName = file.getOriginalFilename();
		if (StringUtil.isBlank(fileName)) {
			throw new RuntimeException("视频名称格式不正确");
		}

		// 校验文件格式
		if (!FileUtil.checkFileTypeByVideo(fileName)) {
			throw new RuntimeException("只能选择视频类型文件");
		}

		if (null == maxSize) {
			maxSize = 20971520L;
		}

		// 校验文件大小，不允许超过 20M，getSize 单位 bytes
		if (file.getSize() > maxSize) {
			String error = "视频过大！请上传小于 " + (maxSize / 1024 / 1024) + "M 的视频";
			throw new RuntimeException(error);
		}
	}

	public static void checkFileFormat(MultipartFile file, Long maxSize) {
		String fileName = file.getOriginalFilename();
		if (StringUtil.isBlank(fileName)) {
			throw new RuntimeException("文件名称格式不正确");
		}

		if (null == maxSize) {
			maxSize = 20971520L;
		}

		// 校验文件大小，不允许超过 20M，getSize 单位 bytes
		if (file.getSize() > maxSize) {
			String error = "文件过大！请上传小于 " + (maxSize / 1024 / 1024) + "M 的文件";
			throw new RuntimeException(error);
		}
	}


	public static String handleFileName(String fileName) {
		fileName = StringUtil.replace(fileName, " ", "_");
		fileName = StringUtil.replace(fileName, "/", "_");
		fileName = StringUtil.replace(fileName, "#", "_");
		fileName = StringUtil.replace(fileName, "%", "_");
		fileName = StringUtil.replace(fileName, "$", "_");
		fileName = StringUtil.replace(fileName, "&", "_");
		return fileName;
	}

	public static boolean checkFileTypeByImage(String fileName) {
		List<String> ignoreSuffix = new ArrayList<>();
		ignoreSuffix.add(".jpg");
		ignoreSuffix.add(".jpeg");
		ignoreSuffix.add(".gif");
		ignoreSuffix.add(".png");
		ignoreSuffix.add(".bmp");
		ignoreSuffix.add(".ico");
		// ignoreSuffix.add(".svg");

		CharSequence[] charSequences = ignoreSuffix.toArray(new CharSequence[ignoreSuffix.size()]);

		return StringUtils.endsWithAny(fileName.toLowerCase(), charSequences);
	}

	public static boolean checkFileTypeByVideo(String fileName) {
		List<String> ignoreSuffix = new ArrayList<>();

		ignoreSuffix.add(".mp4");
		ignoreSuffix.add(".mp3");
		ignoreSuffix.add(".webm");
		ignoreSuffix.add(".flv");
		ignoreSuffix.add(".mkv");
		ignoreSuffix.add(".swf");
		ignoreSuffix.add(".avi");
		ignoreSuffix.add(".rmvb");
		ignoreSuffix.add(".mov");
		ignoreSuffix.add(".wmv");

		CharSequence[] charSequences = ignoreSuffix.toArray(new CharSequence[ignoreSuffix.size()]);

		return StringUtils.endsWithAny(fileName.toLowerCase(), charSequences);
	}

	@SneakyThrows
	public static File multipartToFile(MultipartFile multipart, String fileName) {
		File convFile = new File(System.getProperty("java.io.tmpdir") + File.separator + fileName);
		FileUtils.writeByteArrayToFile(convFile, multipart.getBytes());
		return convFile;
	}

	/**
	 * 判断文件是否存在
	 */
	@SneakyThrows
	public static Boolean checkFile(String filePath, Boolean boolCreate) {
		boolean exist = cn.hutool.core.io.FileUtil.exist(filePath);
		if (!exist) {
			// 不存在，则判断是否需要创建
			if (boolCreate) {
				// 进行创建
				FileUtils.writeStringToFile(new File(filePath), "this is content", StandardCharsets.UTF_8);
			} else {
				return false;
			}
		}
		return true;
	}

	/**
	 * 下载文件
	 */
	public static byte[] downloadFileByUrl(String url) {
		byte[] byteByFileHttpUrl;
		try {
			byteByFileHttpUrl = DownloadUtil.getByteByFileHttpUrl(url);
		} catch (Exception e) {
			// 没抓取到
			return null;
		}
		// 没抓取到返回的也是 null
		return byteByFileHttpUrl;
	}


	/**
	 * 获取真实文件名（自动去掉文件路径）
	 * 带后缀，比如：12c8358098114547a390f0b1a896665e.png
	 */
	public static String getRealFileName(String fileName) {
		return FilenameUtils.getName(fileName);
	}

	/**
	 * 获取真实文件名（自动去掉文件路径）
	 * 不带后缀，比如：12c8358098114547a390f0b1a896665e
	 */
	public static String getBaseFileName(String fileName) {
		return FilenameUtils.getBaseName(fileName);
	}

	/**
	 * 获取文件格式后缀（自动去掉文件路径）
	 * 没有带点，比如：png
	 */
	public static String getFileExtension(String fileName) {
		return FilenameUtils.getExtension(fileName);
	}

	/**
	 * 提取文件路径的单纯路径部分
	 * C:\a\b\c.txt -> C:\a\b\
	 * ~/a/b/c.txt  -> ~/a/b/
	 *
	 * @param fileFullPath
	 * @return
	 */
	public static String getFileFullPath(String fileFullPath) {
		return FilenameUtils.getFullPath(fileFullPath);
	}


	/**
	 * 读取文件内容
	 */
	@SneakyThrows
	public static String readFileToString(String fullFilePath) {
		return FileUtils.readFileToString(new File(fullFilePath), StandardCharsets.UTF_8);
	}

	/**
	 * 读取文件内容，相对于 classpath 下
	 * 路径只需要写：/search/mapping/product-mapping.json
	 * 不要在前面加 classpath: 关键字
	 */
	@SneakyThrows
	public static String readFileToStringByClasspath(String resourceLocation) {
		ClassPathResource resource = new ClassPathResource(resourceLocation);
		InputStream inputStream = resource.getInputStream();
		return FileUtil.getInputStreamToString(inputStream);
	}

	/**
	 * 读取文件内容，相对于 classpath 下
	 * 路径只需要写：search/mapping/product-mapping.json
	 * 不要在前面加 classpath: 关键字
	 */
	@SneakyThrows
	public static String readFileToStringByClasspath2(String resourceLocation) {
		URL url = FileUtil.class.getClassLoader().getResource(resourceLocation);
		if (url == null) {
			return null;
		}
		File file = new File(url.getFile());
		return FileUtils.readFileToString(file, StandardCharsets.UTF_8);
	}


	/**
	 * 创建文件
	 */
	public static File createFile(String filePath) {
		File file;
		try {
			file = new File(filePath);
			File parentDir = file.getParentFile();
			if (!parentDir.exists()) {
				FileUtils.forceMkdir(parentDir);
			}
		} catch (Exception e) {
			log.error("create file failure", e);
			throw new RuntimeException(e);
		}
		return file;
	}

	/**
	 * 创建目录（如果中间的目录不存在也会自动被创建）
	 *
	 * @param fullPath
	 */
	@SneakyThrows
	public static void createDir(String fullPath) {
		FileUtils.forceMkdir(new File(fullPath));
	}

	public static String getUploadFileFullFilePath(HttpServletRequest request, String userLoginName, String coreFilePathName, MultipartFile uploadExcelFileInputId) {
		if (StringUtil.isBlank(coreFilePathName)) {
			coreFilePathName = "notname";
		}
		//上传的最终目录地址：ROOT/upload/excel/当前用户名/年/月/日/小时/
		String dirPath = request.getSession().getServletContext().getRealPath("/") + "upload" + File.separator + coreFilePathName + File.separator + userLoginName + File.separator + DatetimeUtil.getCurrentDateTimeByFilePath();
		//先判断目录地址是否存在，如果不存在则创建一个。
		File dirPathFile = new File(dirPath);
		if (!dirPathFile.exists()) {
			dirPathFile.mkdirs();
		}
		//新名称格式：文件原有名称+上传时间=年月日小时分钟秒
		String originalFileName = uploadExcelFileInputId.getOriginalFilename();
		String newFileName = FilenameUtils.getBaseName(originalFileName) + "_" + DatetimeUtil.formatDate(new Date(), "yyyyMMddHHmmss") + "." + FilenameUtils.getExtension(originalFileName);
		return dirPath + File.separator + newFileName;//新文件完整路径
	}


	public static byte[] readFileToByteArray(final File file) throws IOException {
		return FileUtils.readFileToByteArray(file);
	}

	public static byte[] inputStreamToByteArray(final InputStream inputStream) throws IOException {
		// return ByteStreams.toByteArray(inputStream);// guava 方案
		return IOUtils.toByteArray(inputStream);// commons-io 方案
	}

	/**
	 * 将图片写入到磁盘
	 */
	public static void writeFileToDisk(byte[] bytes, String fileFullPath) {
		try {
			File file = new File(fileFullPath);

			FileUtils.writeByteArrayToFile(file, bytes);

			// FileOutputStream fileOutputStream = new FileOutputStream(file);
			// fileOutputStream.write(bytes);
			// fileOutputStream.flush();
			// fileOutputStream.close();
		} catch (Exception e) {
			ExceptionUtil.printStackTraceAsString(e);
		}
	}

	public static void deleteFile(String fileFullPath) {
		cn.hutool.core.io.FileUtil.del(fileFullPath);
	}


	// =================================================================================
	// 信任所有证书
	private static class MyTrustManager implements X509TrustManager {
		@Override
		public void checkClientTrusted(X509Certificate[] chain, String authType)
				throws CertificateException {
		}

		@Override
		public void checkServerTrusted(X509Certificate[] chain, String authType)
				throws CertificateException {
		}

		@Override
		public X509Certificate[] getAcceptedIssuers() {
			return null;
		}
	}

	private static class MyHostnameVerifier implements HostnameVerifier {
		@Override
		public boolean verify(String hostname, SSLSession session) {
			return true;
		}
	}
	// =================================================================================

}
